﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Sigil.Impl;

namespace Sigil
{
	/// <summary>
	/// Helper for disassembling delegates into a series of Emit operations.
	///
	/// This can be used to inspect delegates, or combine them via Sigil.
	/// </summary>
	/// <typeparam name="DelegateType">The type of delegate being disassembled</typeparam>
	public sealed class Disassembler<DelegateType>
			where DelegateType : class
	{
		private sealed class LabelTracker
		{
			private HashSet<Int32> _MarkAt = new HashSet<Int32>();

			public IEnumerable<Int32> MarkAt { get { return _MarkAt; } }

			public void Mark(Int32 at)
			{
				_MarkAt.Add(at);
			}
		}

		private const Byte ContinueOpcode = 0xFE;
		private static readonly Dictionary<Int32, OpCode> OneByteOps;
		private static readonly Dictionary<Int32, OpCode> TwoByteOps;

		static Disassembler()
		{
			var oneByte = new List<OpCode>();
			var twoByte = new List<OpCode>();

			foreach (var field in typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static))
			{
				var op = (OpCode)field.GetValue(null);

				if (op.Size == 1)
				{
					oneByte.Add(op);
					continue;
				}

				if (op.Size == 2)
				{
					twoByte.Add(op);
					continue;
				}

				throw new Exception("Unexpected op size for " + op);
			}

			OneByteOps = oneByte.ToDictionary(d => (Int32)d.Value, d => d);
			TwoByteOps = twoByte.ToDictionary(d => (Int32)(d.Value & 0xFF), d => d);
		}

		private static void CheckDelegateType()
		{
			var delType = typeof(DelegateType);

			var baseTypes = new HashSet<Type>();
			baseTypes.Add(delType);
			var bType = delType.BaseType;
			while (bType != null)
			{
				baseTypes.Add(bType);
				bType = bType.BaseType;
			}

			if (!baseTypes.Contains(typeof(Delegate)))
			{
				throw new ArgumentException("DelegateType must be a delegate, found " + delType.FullName);
			}
		}

		/// <summary>
		/// Disassembles a delegate into a DisassembledOperations Object.
		/// </summary>
		/// <param name="del">The delegate to disassemble</param>
		public static DisassembledOperations<DelegateType> Disassemble(DelegateType del)
		{
			CheckDelegateType();

			var asDel = (Delegate)(Object)del;

			var method = asDel.Method;
			var body = method.GetMethodBody();

			var cil = body.GetILAsByteArray();
			var locals = body.LocalVariables;
			var @params = asDel.Method.GetParameters();
			var excBlocks = body.ExceptionHandlingClauses;

			var convertedParams = @params.Select(s => Parameter.For(s)).ToList();

			if (asDel.Target != null)
			{
				convertedParams.Insert(0, new Parameter(0, asDel.Target.GetType()));
			}

			var ps = convertedParams;

			var ls =
					new List<LocalVariableInfo>(locals)
							.OrderBy(_ => _.LocalIndex)
							.Select((l, ix) => new Local(null, (UInt16)l.LocalIndex, l.LocalType, null, "_local" + ix, null, 0))
							.ToList();

			var labels = new LabelTracker();
			var asLabels = new List<Label>();
			var needsInference = new List<Tuple<Int32, Operation<DelegateType>>>();
			var ops = GetOperations(asDel.Method.Module, cil, ps, ls, labels, excBlocks, asLabels, needsInference).ToList();

			var markAt = new Dictionary<Int32, Tuple<Int32, String>>();
			foreach (var at in labels.MarkAt)
			{
				var ix = IndexOfOpLastAt(ops, at);
				var name = "_label" + at;
				markAt[ix] = Tuple.Create(at, name);
			}

			foreach (var k in markAt.Keys.OrderByDescending(_ => _).ToList())
			{
				var pair = markAt[k];
				var name = asLabels.Where(l => l.Name == pair.Item2).Single();
				var ix = pair.Item1;

				var mark =
						new Operation<DelegateType>
						{
							IsMarkLabel = true,
							LabelName = name.Name,
							Parameters = new Object[] { name },
							Replay = emit => emit.MarkLabel(name)
						};

				ops.Insert(k, Tuple.Create(ix, mark));
			}

			ops = OrderOperations(ops);

			var prevCount = needsInference.Count;
			while (needsInference.Count > 0)
			{
				ops = InferTypes(ops, needsInference, ps, ls, asLabels);

				needsInference = ops.Where(w => w.Item2.IsOpCode && w.Item2.Parameters == null).ToList();

				if (prevCount == needsInference.Count)
				{
					throw new Exception("Disassembler was unable to infer necessary types");
				}

				prevCount = needsInference.Count;
			}

			var hasThis = (method.CallingConvention | CallingConventions.HasThis) != 0;
			bool usesThis;
			if (hasThis)
			{
				usesThis = false;
				// check to see if we actually use `this`, and if so we can't actually emit
				foreach (var op in ops)
				{
					if (op.Item2.IsOpCode)
					{
						var opcode = op.Item2.OpCode;

						if (opcode == OpCodes.Ldarg_0)
						{
							usesThis = true;
							break;
						}

						var pointsToIntArg =
								opcode == OpCodes.Ldarg ||
								opcode == OpCodes.Ldarga ||
								opcode == OpCodes.Starg;

						if (pointsToIntArg)
						{
							var param = (int)op.Item2.Parameters.ElementAt(0);

							if (param == 0)
							{
								usesThis = true;
								break;
							}
						}

						var pointsToShortArg =
								opcode == OpCodes.Ldarg_S ||
								opcode == OpCodes.Ldarga_S ||
								opcode == OpCodes.Starg_S;

						if (pointsToShortArg)
						{
							var param = (ushort)op.Item2.Parameters.ElementAt(0);

							if (param == 0)
							{
								usesThis = true;
								break;
							}
						}
					}
				}
			}
			else
			{
				usesThis = false;
			}

			var canBeEmitted = asDel.Target == null || !usesThis;

			return
					new DisassembledOperations<DelegateType>(
							ops.Select(d => d.Item2).ToList(),
							ps,
							ls,
							asLabels,
							canBeEmitted
					);
		}

		private static List<Tuple<Int32, Operation<DelegateType>>> InferTypes(
				List<Tuple<Int32, Operation<DelegateType>>> ops,
				List<Tuple<Int32, Operation<DelegateType>>> infer,
				IEnumerable<Parameter> ps,
				IEnumerable<Local> ls,
				IEnumerable<Label> asLabels)
		{
			var tempDisasm = new DisassembledOperations<DelegateType>(
							ops.Select(d => d.Item2).ToList(),
							ps,
							ls,
							asLabels,
							canEmit: false
					);

			var use = new List<OperationResultUsage<DelegateType>>(tempDisasm.Usage);

			ls.Each(l => l.SetOwner(null));
			asLabels.Each(l => l.SetOwner(null));

			var inferOps = infer.Select(s => s.Item2.OpCode).Distinct();

			var traced = use.Where(u => inferOps.Contains(u.ProducesResult.OpCode));

			foreach (var t in traced)
			{
				var toReplace = infer[0];

				var couldBe = use.Where(u => u.ResultUsedBy.Contains(t.ProducesResult)).ToList();
				var couldBeTypes = couldBe.SelectMany(c => c.TypesProduced).Distinct().ToList();

				var op = MakeInferredReplayableOp(t.ProducesResult.OpCode, couldBeTypes, toReplace.Item2.Prefixes);

				infer.RemoveAt(0);

				if (op != null)
				{
					var replaceAt = ops.IndexOf(toReplace);
					ops[replaceAt] = Tuple.Create(toReplace.Item1, op);
				}
			}

			return ops;
		}

		private static Operation<DelegateType> MakeInferredReplayableOp(OpCode op, IEnumerable<TypeOnStack> consumesType, PrefixTracker prefixes)
		{
			if (consumesType.Any(c => c.Type == typeof(WildcardType))) return null;

			if (op == OpCodes.Ldlen)
			{
				var elem = consumesType.Single().Type.GetElementType();

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { elem },
							Replay = emit => emit.LoadLength(elem)
						};
			}

			if (op == OpCodes.Ldelem_Ref)
			{
				var elem = consumesType.Where(x => x.IsArray).Single().Type.GetElementType();

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { elem },
							Replay = emit => emit.LoadElement(elem)
						};
			}

			if (op == OpCodes.Stelem_Ref)
			{
				var vals = consumesType.Where(x => !x.Type.IsValueType).ToList();
				var arr = vals.OrderByDescending(v => v.IsArray ? v.Type.GetArrayRank() : 0).First();

				var elem = arr.Type.GetElementType();

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { elem },
							Replay = emit => emit.StoreElement(elem)
						};
			}

			if (op == OpCodes.Ldind_Ref)
			{
				var elem = consumesType.Single().Type.GetElementType();
				var isVolatile = prefixes.HasVolatile;
				Int32? unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { elem, isVolatile, unaligned },
							Replay = emit => emit.LoadIndirect(elem, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Stind_Ref)
			{
				var arr = consumesType.OrderByDescending(v => v.IsArray ? v.Type.GetArrayRank() : 0).First();
				var elem = arr.Type.GetElementType();
				var isVolatile = prefixes.HasVolatile;
				Int32? unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { elem, isVolatile, unaligned },
							Replay = emit => emit.StoreIndirect(elem, isVolatile, unaligned)
						};
			}

			throw new Exception("Encountered unexpected operation [" + op + "] which requires type inferencing");
		}

		private static List<Tuple<Int32, Operation<DelegateType>>> OrderOperations(List<Tuple<Int32, Operation<DelegateType>>> ops)
		{
			var ret = new List<Tuple<Int32, Operation<DelegateType>>>();

			var grouped = ops.GroupBy(g => g.Item1);

			foreach (var group in grouped.OrderBy(o => o.Key))
			{
				var inOrder =
						group.Select(_ => _.Item2).OrderBy<Operation<DelegateType>, Int32>(
								op =>
								{
									if (op.IsCatchBlockEnd) return -1000000;
									if (op.IsFinallyBlockEnd) return -10000;
									if (op.IsExceptionBlockEnd) return -1000;

									if (op.IsExceptionBlockStart) return -100;
									if (op.IsCatchBlockStart) return -10;
									if (op.IsFinallyBlockStart) return -1;

									if (op.IsMarkLabel) return 0;

									return 1;
								}
						);

				foreach (var i in inOrder)
				{
					ret.Add(Tuple.Create(group.Key, i));
				}
			}

			return ret;
		}

		private static Int32 IndexOfOpLastAt(IEnumerable<Tuple<Int32, Operation<DelegateType>>> ops, Int32 ix)
		{
			Boolean foundIt = false;
			var i = 0;
			foreach (var x in ops)
			{
				if (x.Item1 == ix)
				{
					foundIt = true;
				}

				if (foundIt && x.Item1 != ix)
				{
					return i - 1;
				}

				i++;
			}

			if (foundIt)
			{
				return i - 1;
			}

			throw new Exception("Couldn't find label position in operations for " + ix);
		}

		private static Int32 DeclarationNumber(Dictionary<Int32, List<ExceptionHandlingClause>> start, ExceptionHandlingClause exc)
		{
			var order = start.Keys.Select(_ => _).OrderBy(_ => _).ToList();

			Int32 ct = 0;

			foreach (var k in order)
			{
				var set = start[k];
				var ix = set.IndexOf(exc);

				if (ix == -1)
				{
					ct += set.Count;
				}
				else
				{
					ct += ix;
					return ct;
				}
			}

			throw new Exception("Couldn't find [" + exc + "] in starting exception blocks");
		}

		private static IEnumerable<Tuple<Int32, Operation<DelegateType>>> GetOperations(
				Module mod,
				Byte[] cil,
				IEnumerable<Parameter> ps,
				IEnumerable<Local> ls,
				LabelTracker labels,
				IList<ExceptionHandlingClause> exceptions,
				List<Label> labelAccumulator,
				List<Tuple<Int32, Operation<DelegateType>>> needsInference)
		{
			var exceptionStart = new Dictionary<Int32, List<ExceptionHandlingClause>>();
			var exceptionEnd = new Dictionary<Int32, List<ExceptionHandlingClause>>();
			var catchStart = new Dictionary<Int32, List<ExceptionHandlingClause>>();
			var catchEnd = new Dictionary<Int32, List<ExceptionHandlingClause>>();
			var finallyStart = new Dictionary<Int32, List<ExceptionHandlingClause>>();
			var finallyEnd = new Dictionary<Int32, List<ExceptionHandlingClause>>();

			var activeExceptionBlocks = new Dictionary<ExceptionHandlingClause, String>();
			var activeCatchBlocks = new Dictionary<ExceptionHandlingClause, String>();
			var activeFinallyBlocks = new Dictionary<ExceptionHandlingClause, String>();

			foreach (var exc in exceptions)
			{
				List<ExceptionHandlingClause> eStart, eEnd;

				if (!exceptionStart.TryGetValue(exc.TryOffset, out eStart))
				{
					exceptionStart[exc.TryOffset] = eStart = new List<ExceptionHandlingClause>();
				}

				if (!exceptionEnd.TryGetValue(exc.HandlerOffset + exc.HandlerLength, out eEnd))
				{
					exceptionEnd[exc.HandlerOffset + exc.HandlerLength] = eEnd = new List<ExceptionHandlingClause>();
				}

				eStart.Add(exc);
				eEnd.Add(exc);

				if (exc.Flags == ExceptionHandlingClauseOptions.Clause)
				{
					List<ExceptionHandlingClause> cStart, cEnd;

					if (!catchStart.TryGetValue(exc.HandlerOffset, out cStart))
					{
						catchStart[exc.HandlerOffset] = cStart = new List<ExceptionHandlingClause>();
					}

					if (!catchEnd.TryGetValue(exc.HandlerOffset + exc.HandlerLength, out cEnd))
					{
						catchEnd[exc.HandlerOffset + exc.HandlerLength] = cEnd = new List<ExceptionHandlingClause>();
					}

					cStart.Add(exc);
					cEnd.Add(exc);

					continue;
				}

				if (exc.Flags == ExceptionHandlingClauseOptions.Finally)
				{
					List<ExceptionHandlingClause> fStart, fEnd;

					if (!finallyStart.TryGetValue(exc.HandlerOffset, out fStart))
					{
						finallyStart[exc.HandlerOffset] = fStart = new List<ExceptionHandlingClause>();
					}

					if (!finallyEnd.TryGetValue(exc.HandlerOffset + exc.HandlerLength, out fEnd))
					{
						finallyEnd[exc.HandlerOffset + exc.HandlerLength] = fEnd = new List<ExceptionHandlingClause>();
					}

					fStart.Add(exc);
					fEnd.Add(exc);

					continue;
				}

				throw new InvalidOperationException("Unexpected exception handling clause, Sigil only supports try/catch/finally.");
			}

			foreach (var k in exceptionStart.Keys.ToList())
			{
				exceptionStart[k] = exceptionStart[k].OrderByDescending(x => x.TryLength + x.HandlerLength).ToList();
			}

			foreach (var k in exceptionEnd.Keys.ToList())
			{
				exceptionEnd[k] = exceptionEnd[k].OrderBy(x => x.TryLength + x.HandlerLength).ToList();
			}

			foreach (var k in catchStart.Keys.ToList())
			{
				catchStart[k] = catchStart[k].OrderBy(x => x.TryLength + x.HandlerLength).ToList();
			}

			foreach (var k in catchEnd.Keys.ToList())
			{
				catchEnd[k] = catchEnd[k].OrderBy(x => x.TryLength + x.HandlerLength).ToList();
			}

			foreach (var k in finallyStart.Keys.ToList())
			{
				finallyStart[k] = finallyStart[k].OrderBy(x => x.TryLength + x.HandlerLength).ToList();
			}

			foreach (var k in finallyEnd.Keys.ToList())
			{
				finallyEnd[k] = finallyEnd[k].OrderBy(x => x.TryLength + x.HandlerLength).ToList();
			}

			var parameterLookup = new Dictionary<Int32, Parameter>();
			var localLookup = new Dictionary<Int32, Local>();

			foreach (var p in ps)
			{
				parameterLookup[p.Position] = p;
			}

			foreach (var l in ls)
			{
				localLookup[l.Index] = l;
			}

			var ret = new List<Tuple<Int32, Operation<DelegateType>>>();
			var prefixes = new PrefixTracker();

			CheckForExceptionOperations(0, exceptionStart, exceptionEnd, catchStart, catchEnd, finallyStart, finallyEnd, activeExceptionBlocks, activeCatchBlocks, activeFinallyBlocks, ret);

			Int32? gap = null;
			Int32 i = 0;
			while (i < cil.Length)
			{
				var startsAt = i;
				Operation<DelegateType> op;
				i += ReadOp(mod, cil, i, parameterLookup, localLookup, prefixes, labels, labelAccumulator, out op);

				if (op != null)
				{
					if (!op.IsIgnored)
					{
						var toAdd = Tuple.Create(gap ?? startsAt, op);

						ret.Add(toAdd);

						if (op.Parameters == null)
						{
							needsInference.Add(toAdd);
						}
					}

					prefixes.Clear();
					gap = null;
				}
				else
				{
					gap = gap ?? startsAt;
				}

				CheckForExceptionOperations(i, exceptionStart, exceptionEnd, catchStart, catchEnd, finallyStart, finallyEnd, activeExceptionBlocks, activeCatchBlocks, activeFinallyBlocks, ret);
			}

			return ret;
		}

		private static void CheckForExceptionOperations(
				Int32 i,
				Dictionary<Int32, List<ExceptionHandlingClause>> exceptionStart,
				Dictionary<Int32, List<ExceptionHandlingClause>> exceptionEnd,
				Dictionary<Int32, List<ExceptionHandlingClause>> catchStart,
				Dictionary<Int32, List<ExceptionHandlingClause>> catchEnd,
				Dictionary<Int32, List<ExceptionHandlingClause>> finallyStart,
				Dictionary<Int32, List<ExceptionHandlingClause>> finallyEnd,
				Dictionary<ExceptionHandlingClause, String> activeExceptionBlocks,
				Dictionary<ExceptionHandlingClause, String> activeCatchBlocks,
				Dictionary<ExceptionHandlingClause, String> activeFinallyBlocks,
				List<Tuple<Int32, Operation<DelegateType>>> ret)
		{
			if (catchEnd.ContainsKey(i))
			{
				foreach (var exc in catchEnd[i])
				{
					var c = activeCatchBlocks[exc];

					ret.Add(
							Tuple.Create(
									i,
									new Operation<DelegateType>
									{
										IsCatchBlockEnd = true,
										Parameters = new Object[0],
										Replay = emit => emit.EndCatchBlock(c)
									}
							)
					);

					activeCatchBlocks.Remove(exc);
				}
			}

			if (finallyEnd.ContainsKey(i))
			{
				foreach (var exc in finallyEnd[i])
				{
					var f = activeFinallyBlocks[exc];

					ret.Add(
							Tuple.Create(
									i,
									new Operation<DelegateType>
									{
										IsFinallyBlockEnd = true,
										Parameters = new Object[0],
										Replay = emit => emit.EndFinallyBlock(f)
									}
							)
					);

					activeFinallyBlocks.Remove(exc);
				}
			}

			// Must be checked last as catch & finally must end before the exception block overall
			if (exceptionEnd.ContainsKey(i))
			{
				foreach (var exc in exceptionEnd[i])
				{
					var name = activeExceptionBlocks[exc];

					ret.Add(
							Tuple.Create(
									i,
									new Operation<DelegateType>
									{
										IsExceptionBlockEnd = true,
										Parameters = new Object[0],
										Replay = emit => emit.EndExceptionBlock(name)
									}
							)
					);

					activeExceptionBlocks.Remove(exc);
				}
			}

			if (exceptionStart.ContainsKey(i))
			{
				foreach (var exc in exceptionStart[i])
				{
					var name = "__exc-" + Guid.NewGuid();

					ret.Add(
							Tuple.Create(
									i,
									new Operation<DelegateType>
									{
										IsExceptionBlockStart = true,
										Parameters = new Object[0],
										Replay = emit => emit.BeginExceptionBlock(name)
									}
							)
					);

					activeExceptionBlocks[exc] = name;
				}
			}

			if (catchStart.ContainsKey(i))
			{
				foreach (var exc in catchStart[i])
				{
					var name = activeExceptionBlocks[exc];

					var catchName = "__catch-" + Guid.NewGuid();

					ret.Add(
							Tuple.Create(
									i,
									new Operation<DelegateType>
									{
										IsCatchBlockStart = true,
										Parameters = new Object[0],
										Replay = emit => emit.BeginCatchBlock(name, exc.CatchType, catchName)
									}
							)
					);

					activeCatchBlocks[exc] = catchName;
				}
			}

			if (finallyStart.ContainsKey(i))
			{
				foreach (var exc in finallyStart[i])
				{
					var name = activeExceptionBlocks[exc];

					var finallyName = "__finally-" + Guid.NewGuid();

					ret.Add(
							Tuple.Create(
									i,
									new Operation<DelegateType>
									{
										IsFinallyBlockStart = true,
										Parameters = new Object[0],
										Replay = emit => emit.BeginFinallyBlock(name, finallyName)
									}
							)
					);

					activeFinallyBlocks[exc] = finallyName;
				}
			}
		}

		private static Int32 ReadOp(
				Module mod,
				Byte[] cil,
				Int32 ix,
				IDictionary<Int32, Parameter> pLookup,
				IDictionary<Int32, Local> lLookup,
				PrefixTracker prefixes,
				LabelTracker labels,
				List<Label> labelAccumulator,
				out Operation<DelegateType> op)
		{
			Int32 advance = 0;

			OpCode opcode;
			Byte first = cil[ix];

			if (first == ContinueOpcode)
			{
				var next = cil[ix + 1];

				opcode = TwoByteOps[next];
				advance += 2;
			}
			else
			{
				opcode = OneByteOps[first];
				advance++;
			}

			var operand = ReadOperands(mod, opcode, cil, ix, ix + advance, pLookup, lLookup, ref advance);

			op = MakeReplayableOperation(opcode, operand, prefixes, labels, labelAccumulator, lLookup);

			return advance;
		}

		private static Label ChooseLabel(Int32 absAddr, LabelTracker labels, List<Label> labelAccumulator)
		{
			var name = "_label" + absAddr;

			var ret = labelAccumulator.Where(l => l.Name == name).SingleOrDefault();

			if (ret == null)
			{
				ret = new Label(null, null, name);
				labelAccumulator.Add(ret);
			}

			labels.Mark(absAddr);
			return ret;
		}

		private static Operation<DelegateType> MakeReplayableOperation(
				OpCode op,
				Object[] operands,
				PrefixTracker prefixes,
				LabelTracker labels,
				List<Label> labelAccumulator,
				IDictionary<Int32, Local> locals)
		{
			if (op == OpCodes.Add)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Add()
						};
			}

			if (op == OpCodes.Add_Ovf)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.AddOverflow()
						};
			}

			if (op == OpCodes.Add_Ovf_Un)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.UnsignedAddOverflow()
						};
			}

			if (op == OpCodes.And)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.And()
						};
			}

			if (op == OpCodes.Arglist)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.ArgumentList()
						};
			}

			if (op == OpCodes.Beq || op == OpCodes.Beq_S)
			{
				var absAddr = (Int32)operands[0];
				var label = ChooseLabel(absAddr, labels, labelAccumulator);

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { label },
							Replay = emit => emit.BranchIfEqual(label)
						};
			}

			if (op == OpCodes.Bge || op == OpCodes.Bge_S)
			{
				var absAddr = (Int32)operands[0];
				var label = ChooseLabel(absAddr, labels, labelAccumulator);

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { label },
							Replay = emit => emit.BranchIfGreaterOrEqual(label)
						};
			}

			if (op == OpCodes.Bge_Un || op == OpCodes.Bge_Un_S)
			{
				var absAddr = (Int32)operands[0];
				var label = ChooseLabel(absAddr, labels, labelAccumulator);

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { label },
							Replay = emit => emit.UnsignedBranchIfGreaterOrEqual(label)
						};
			}

			if (op == OpCodes.Bgt || op == OpCodes.Bgt_S)
			{
				var absAddr = (Int32)operands[0];
				var label = ChooseLabel(absAddr, labels, labelAccumulator);

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { label },
							Replay = emit => emit.BranchIfGreater(label)
						};
			}

			if (op == OpCodes.Bgt_Un || op == OpCodes.Bgt_Un_S)
			{
				var absAddr = (Int32)operands[0];
				var label = ChooseLabel(absAddr, labels, labelAccumulator);

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { label },
							Replay = emit => emit.UnsignedBranchIfGreater(label)
						};
			}

			if (op == OpCodes.Ble || op == OpCodes.Ble_S)
			{
				var absAddr = (Int32)operands[0];
				var label = ChooseLabel(absAddr, labels, labelAccumulator);

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { label },
							Replay = emit => emit.BranchIfLessOrEqual(label)
						};
			}

			if (op == OpCodes.Ble_Un || op == OpCodes.Ble_Un_S)
			{
				var absAddr = (Int32)operands[0];
				var label = ChooseLabel(absAddr, labels, labelAccumulator);

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { label },
							Replay = emit => emit.UnsignedBranchIfLessOrEqual(label)
						};
			}

			if (op == OpCodes.Blt || op == OpCodes.Blt_S)
			{
				var absAddr = (Int32)operands[0];
				var label = ChooseLabel(absAddr, labels, labelAccumulator);

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { label },
							Replay = emit => emit.BranchIfLess(label)
						};
			}

			if (op == OpCodes.Blt_Un || op == OpCodes.Blt_Un_S)
			{
				var absAddr = (Int32)operands[0];
				var label = ChooseLabel(absAddr, labels, labelAccumulator);

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { label },
							Replay = emit => emit.UnsignedBranchIfLess(label)
						};
			}

			if (op == OpCodes.Bne_Un || op == OpCodes.Bne_Un_S)
			{
				var absAddr = (Int32)operands[0];
				var label = ChooseLabel(absAddr, labels, labelAccumulator);

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { label },
							Replay = emit => emit.UnsignedBranchIfNotEqual(label)
						};
			}

			if (op == OpCodes.Box)
			{
				var valType = (Type)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { valType },
							Replay = emit => emit.Box(valType)
						};
			}

			if (op == OpCodes.Br || op == OpCodes.Br_S)
			{
				var absAddr = (Int32)operands[0];
				var label = ChooseLabel(absAddr, labels, labelAccumulator);

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { label },
							Replay = emit => emit.Branch(label)
						};
			}

			if (op == OpCodes.Break)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Break()
						};
			}

			if (op == OpCodes.Brfalse || op == OpCodes.Brfalse_S)
			{
				var absAddr = (Int32)operands[0];
				var label = ChooseLabel(absAddr, labels, labelAccumulator);

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { label },
							Replay = emit => emit.BranchIfFalse(label)
						};
			}

			if (op == OpCodes.Brtrue || op == OpCodes.Brtrue_S)
			{
				var absAddr = (Int32)operands[0];
				var label = ChooseLabel(absAddr, labels, labelAccumulator);

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { label },
							Replay = emit => emit.BranchIfTrue(label)
						};
			}

			if (op == OpCodes.Call)
			{
				var mem = (MemberInfo)operands[0];
				var mtd = (MethodInfo)mem;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { mtd },
							Replay = emit => emit.Call(mtd, null)
						};
			}

			if (op == OpCodes.Calli)
			{
				throw new NotImplementedException("Calli is not supported in Sigil.Disassembler");
			}

			if (op == OpCodes.Callvirt)
			{
				var mem = (MemberInfo)operands[0];
				var mtd = (MethodInfo)mem;
				var cnstd = prefixes.HasConstrained ? prefixes.Constrained : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { mtd, cnstd, null },
							Replay = emit => emit.CallVirtual(mtd, cnstd, null)
						};
			}

			if (op == OpCodes.Castclass)
			{
				var type = (Type)operands[0];

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.CastClass(type)
						};
			}

			if (op == OpCodes.Ceq)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.CompareEqual()
						};
			}

			if (op == OpCodes.Cgt)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.CompareGreaterThan()
						};
			}

			if (op == OpCodes.Cgt_Un)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.UnsignedCompareGreaterThan()
						};
			}

			if (op == OpCodes.Ckfinite)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.CheckFinite()
						};
			}

			if (op == OpCodes.Clt)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.CompareLessThan()
						};
			}

			if (op == OpCodes.Clt_Un)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.UnsignedCompareLessThan()
						};
			}

			if (op == OpCodes.Constrained)
			{
				var type = (Type)operands[0];
				prefixes.SetConstrained(type);

				return null;
			}

			if (op == OpCodes.Conv_I)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Convert(typeof(IntPtr))
						};
			}

			if (op == OpCodes.Conv_I1)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Convert(typeof(sbyte))
						};
			}

			if (op == OpCodes.Conv_I2)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Convert(typeof(Int16))
						};
			}

			if (op == OpCodes.Conv_I4)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Convert(typeof(Int32))
						};
			}

			if (op == OpCodes.Conv_I8)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Convert(typeof(Int64))
						};
			}

			if (op == OpCodes.Conv_Ovf_I)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.ConvertOverflow(typeof(IntPtr))
						};
			}

			if (op == OpCodes.Conv_Ovf_I_Un)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.UnsignedConvertOverflow(typeof(IntPtr))
						};
			}

			if (op == OpCodes.Conv_Ovf_I1)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.ConvertOverflow(typeof(sbyte))
						};
			}

			if (op == OpCodes.Conv_Ovf_I1_Un)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.UnsignedConvertOverflow(typeof(sbyte))
						};
			}

			if (op == OpCodes.Conv_Ovf_I2)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.ConvertOverflow(typeof(Int16))
						};
			}

			if (op == OpCodes.Conv_Ovf_I2_Un)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.UnsignedConvertOverflow(typeof(Int16))
						};
			}

			if (op == OpCodes.Conv_Ovf_I4)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.ConvertOverflow(typeof(Int32))
						};
			}

			if (op == OpCodes.Conv_Ovf_I4_Un)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.UnsignedConvertOverflow(typeof(Int32))
						};
			}

			if (op == OpCodes.Conv_Ovf_I8)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.ConvertOverflow(typeof(Int64))
						};
			}

			if (op == OpCodes.Conv_Ovf_I8_Un)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.UnsignedConvertOverflow(typeof(Int64))
						};
			}

			if (op == OpCodes.Conv_Ovf_U)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.ConvertOverflow(typeof(UIntPtr))
						};
			}

			if (op == OpCodes.Conv_Ovf_U_Un)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.UnsignedConvertOverflow(typeof(UIntPtr))
						};
			}

			if (op == OpCodes.Conv_Ovf_U1)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.ConvertOverflow(typeof(Byte))
						};
			}

			if (op == OpCodes.Conv_Ovf_U1_Un)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.UnsignedConvertOverflow(typeof(Byte))
						};
			}

			if (op == OpCodes.Conv_Ovf_U2)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.ConvertOverflow(typeof(UInt16))
						};
			}

			if (op == OpCodes.Conv_Ovf_U2_Un)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.UnsignedConvertOverflow(typeof(UInt16))
						};
			}

			if (op == OpCodes.Conv_Ovf_U4)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.ConvertOverflow(typeof(UInt32))
						};
			}

			if (op == OpCodes.Conv_Ovf_U4_Un)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.UnsignedConvertOverflow(typeof(UInt32))
						};
			}

			if (op == OpCodes.Conv_Ovf_U8)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.ConvertOverflow(typeof(UInt64))
						};
			}

			if (op == OpCodes.Conv_Ovf_U8_Un)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.UnsignedConvertOverflow(typeof(UInt64))
						};
			}

			if (op == OpCodes.Conv_R_Un)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.UnsignedConvertToFloat()
						};
			}

			if (op == OpCodes.Conv_R4)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Convert(typeof(Single))
						};
			}

			if (op == OpCodes.Conv_R8)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Convert(typeof(Double))
						};
			}

			if (op == OpCodes.Conv_U)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Convert(typeof(UIntPtr))
						};
			}

			if (op == OpCodes.Conv_U1)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Convert(typeof(Byte))
						};
			}

			if (op == OpCodes.Conv_U2)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Convert(typeof(UInt16))
						};
			}

			if (op == OpCodes.Conv_U4)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Convert(typeof(UInt32))
						};
			}

			if (op == OpCodes.Conv_U8)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Convert(typeof(UInt64))
						};
			}

			if (op == OpCodes.Cpblk)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.CopyBlock(false, null)
						};
			}

			if (op == OpCodes.Cpobj)
			{
				var type = (Type)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.CopyObject(type)
						};
			}

			if (op == OpCodes.Div)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Divide()
						};
			}

			if (op == OpCodes.Div_Un)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.UnsignedDivide()
						};
			}

			if (op == OpCodes.Dup)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Duplicate()
						};
			}

			if (op == OpCodes.Endfilter)
			{
				throw new InvalidOperationException("Sigil does not support fault blocks, or the Endfilter opcode");
			}

			if (op == OpCodes.Endfinally)
			{
				// Endfinally isn't emitted directly by ILGenerator or Sigil; it's implicit in EndFinallyBlock() calls
				return
						new Operation<DelegateType>
						{
							IsIgnored = true
						};
			}

			if (op == OpCodes.Initblk)
			{
				var isVolatile = prefixes.HasVolatile;
				Int32? unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { isVolatile, unaligned },
							Replay = emit => emit.InitializeBlock(isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Initobj)
			{
				var type = (Type)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.InitializeObject(type)
						};
			}

			if (op == OpCodes.Isinst)
			{
				var type = (Type)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.IsInstance(type)
						};
			}

			if (op == OpCodes.Jmp)
			{
				var mtd = (MethodInfo)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { mtd },
							Replay = emit => emit.Jump(mtd)
						};
			}

			if (op == OpCodes.Ldarg)
			{
				UInt16 ix = (UInt16)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { ix },
							Replay = emit => emit.LoadArgument(ix)
						};
			}

			if (op == OpCodes.Ldarg_0)
			{
				UInt16 ix = 0;
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { ix },
							Replay = emit => emit.LoadArgument(ix)
						};
			}

			if (op == OpCodes.Ldarg_1)
			{
				UInt16 ix = 1;
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { ix },
							Replay = emit => emit.LoadArgument(ix)
						};
			}

			if (op == OpCodes.Ldarg_2)
			{
				UInt16 ix = 2;
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { ix },
							Replay = emit => emit.LoadArgument(ix)
						};
			}

			if (op == OpCodes.Ldarg_3)
			{
				UInt16 ix = 3;
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { ix },
							Replay = emit => emit.LoadArgument(ix)
						};
			}

			if (op == OpCodes.Ldarg_S)
			{
				UInt16 ix = (Byte)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { ix },
							Replay = emit => emit.LoadArgument(ix)
						};
			}

			if (op == OpCodes.Ldarga)
			{
				UInt16 ix = (UInt16)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { ix },
							Replay = emit => emit.LoadArgumentAddress(ix)
						};
			}

			if (op == OpCodes.Ldarga_S)
			{
				UInt16 ix = (Byte)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { ix },
							Replay = emit => emit.LoadArgumentAddress(ix)
						};
			}

			if (op == OpCodes.Ldc_I4)
			{
				Int32 c = (Int32)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { c },
							Replay = emit => emit.LoadConstant(c)
						};
			}

			if (op == OpCodes.Ldc_I4_0)
			{
				Int32 c = 0;
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { c },
							Replay = emit => emit.LoadConstant(c)
						};
			}

			if (op == OpCodes.Ldc_I4_1)
			{
				Int32 c = 1;
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { c },
							Replay = emit => emit.LoadConstant(c)
						};
			}

			if (op == OpCodes.Ldc_I4_2)
			{
				Int32 c = 2;
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { c },
							Replay = emit => emit.LoadConstant(c)
						};
			}

			if (op == OpCodes.Ldc_I4_3)
			{
				Int32 c = 3;
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { c },
							Replay = emit => emit.LoadConstant(c)
						};
			}

			if (op == OpCodes.Ldc_I4_4)
			{
				Int32 c = 4;
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { c },
							Replay = emit => emit.LoadConstant(c)
						};
			}

			if (op == OpCodes.Ldc_I4_5)
			{
				Int32 c = 5;
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { c },
							Replay = emit => emit.LoadConstant(c)
						};
			}

			if (op == OpCodes.Ldc_I4_6)
			{
				Int32 c = 6;
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { c },
							Replay = emit => emit.LoadConstant(c)
						};
			}

			if (op == OpCodes.Ldc_I4_7)
			{
				Int32 c = 7;
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { c },
							Replay = emit => emit.LoadConstant(c)
						};
			}

			if (op == OpCodes.Ldc_I4_8)
			{
				Int32 c = 8;
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { c },
							Replay = emit => emit.LoadConstant(c)
						};
			}

			if (op == OpCodes.Ldc_I4_M1)
			{
				Int32 c = -1;
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { c },
							Replay = emit => emit.LoadConstant(c)
						};
			}

			if (op == OpCodes.Ldc_I4_S)
			{
				Int32 c = (sbyte)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { c },
							Replay = emit => emit.LoadConstant(c)
						};
			}

			if (op == OpCodes.Ldc_I8)
			{
				Int64 c = (Int64)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { c },
							Replay = emit => emit.LoadConstant(c)
						};
			}

			if (op == OpCodes.Ldc_R4)
			{
				Single c = (Single)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { c },
							Replay = emit => emit.LoadConstant(c)
						};
			}

			if (op == OpCodes.Ldc_R8)
			{
				Double c = (Double)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { c },
							Replay = emit => emit.LoadConstant(c)
						};
			}

			if (op == OpCodes.Ldelem)
			{
				var type = (Type)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.LoadElement(type)
						};
			}

			if (op == OpCodes.Ldelem_I)
			{
				var type = typeof(IntPtr);
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.LoadElement(type)
						};
			}

			if (op == OpCodes.Ldelem_I1)
			{
				var type = typeof(sbyte);
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.LoadElement(type)
						};
			}

			if (op == OpCodes.Ldelem_I2)
			{
				var type = typeof(Int16);
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.LoadElement(type)
						};
			}

			if (op == OpCodes.Ldelem_I4)
			{
				var type = typeof(Int32);
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.LoadElement(type)
						};
			}

			if (op == OpCodes.Ldelem_I8)
			{
				var type = typeof(Int64);
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.LoadElement(type)
						};
			}

			if (op == OpCodes.Ldelem_R4)
			{
				var type = typeof(Single);
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.LoadElement(type)
						};
			}

			if (op == OpCodes.Ldelem_R8)
			{
				var type = typeof(Double);
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.LoadElement(type)
						};
			}

			if (op == OpCodes.Ldelem_Ref)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = null,
							Replay = emit => emit.LoadElement(typeof(WildcardType))
						};
			}

			if (op == OpCodes.Ldelem_U1)
			{
				var type = typeof(Byte);
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.LoadElement(type)
						};
			}

			if (op == OpCodes.Ldelem_U2)
			{
				var type = typeof(UInt16);
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.LoadElement(type)
						};
			}

			if (op == OpCodes.Ldelem_U4)
			{
				var type = typeof(UInt32);
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.LoadElement(type)
						};
			}

			if (op == OpCodes.Ldelema)
			{
				var type = (Type)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.LoadElementAddress(type)
						};
			}

			if (op == OpCodes.Ldfld)
			{
				var fld = (FieldInfo)operands[0];
				var isVolatile = prefixes.HasVolatile;
				Int32? unalgined = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { fld, isVolatile, unalgined },
							Replay = emit => emit.LoadField(fld, isVolatile, unalgined)
						};
			}

			if (op == OpCodes.Ldflda)
			{
				var fld = (FieldInfo)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { fld },
							Replay = emit => emit.LoadFieldAddress(fld)
						};
			}

			if (op == OpCodes.Ldftn)
			{
				var mtd = (MethodInfo)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { mtd },
							Replay = emit => emit.LoadFunctionPointer(mtd)
						};
			}

			if (op == OpCodes.Ldind_I)
			{
				var type = typeof(IntPtr);
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type, isVolatile, unaligned },
							Replay = emit => emit.LoadIndirect(type, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Ldind_I1)
			{
				var type = typeof(sbyte);
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type, isVolatile, unaligned },
							Replay = emit => emit.LoadIndirect(type, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Ldind_I2)
			{
				var type = typeof(Int16);
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type, isVolatile, unaligned },
							Replay = emit => emit.LoadIndirect(type, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Ldind_I4)
			{
				var type = typeof(Int32);
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type, isVolatile, unaligned },
							Replay = emit => emit.LoadIndirect(type, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Ldind_I8)
			{
				var type = typeof(Int64);
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type, isVolatile, unaligned },
							Replay = emit => emit.LoadIndirect(type, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Ldind_R4)
			{
				var type = typeof(Single);
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type, isVolatile, unaligned },
							Replay = emit => emit.LoadIndirect(type, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Ldind_R8)
			{
				var type = typeof(Double);
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type, isVolatile, unaligned },
							Replay = emit => emit.LoadIndirect(type, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Ldind_Ref)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = null,
							Replay = emit => emit.LoadIndirect<WildcardType>(),

							Prefixes = prefixes.Clone()
						};
			}

			if (op == OpCodes.Ldind_U1)
			{
				var type = typeof(Byte);
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type, isVolatile, unaligned },
							Replay = emit => emit.LoadIndirect(type, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Ldind_U2)
			{
				var type = typeof(UInt16);
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type, isVolatile, unaligned },
							Replay = emit => emit.LoadIndirect(type, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Ldind_U4)
			{
				var type = typeof(UInt32);
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type, isVolatile, unaligned },
							Replay = emit => emit.LoadIndirect(type, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Ldlen)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = null,
							Replay = emit => emit.LoadLength<WildcardType>()
						};
			}

			if (op == OpCodes.Ldloc)
			{
				UInt16 ix = (UInt16)operands[0];
				var loc = locals[ix];

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { loc },
							Replay = emit => emit.LoadLocal(loc)
						};
			}

			if (op == OpCodes.Ldloc_0)
			{
				UInt16 ix = 0;
				var loc = locals[ix];

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { loc },
							Replay = emit => emit.LoadLocal(loc)
						};
			}

			if (op == OpCodes.Ldloc_1)
			{
				UInt16 ix = 1;
				var loc = locals[ix];

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { loc },
							Replay = emit => emit.LoadLocal(loc)
						};
			}

			if (op == OpCodes.Ldloc_2)
			{
				UInt16 ix = 2;
				var loc = locals[ix];

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { loc },
							Replay = emit => emit.LoadLocal(loc)
						};
			}

			if (op == OpCodes.Ldloc_3)
			{
				UInt16 ix = 3;
				var loc = locals[ix];

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { loc },
							Replay = emit => emit.LoadLocal(loc)
						};
			}

			if (op == OpCodes.Ldloc_S)
			{
				UInt16 ix = (Byte)operands[0];
				var loc = locals[ix];

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { loc },
							Replay = emit => emit.LoadLocal(loc)
						};
			}

			if (op == OpCodes.Ldloca)
			{
				UInt16 ix = (UInt16)operands[0];
				var loc = locals[ix];

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { loc },
							Replay = emit => emit.LoadLocalAddress(loc)
						};
			}

			if (op == OpCodes.Ldloca_S)
			{
				UInt16 ix = (Byte)operands[0];
				var loc = locals[ix];

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { loc },
							Replay = emit => emit.LoadLocalAddress(loc)
						};
			}

			if (op == OpCodes.Ldnull)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.LoadNull()
						};
			}

			if (op == OpCodes.Ldobj)
			{
				var type = (Type)operands[0];
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type, isVolatile, unaligned },
							Replay = emit => emit.LoadObject(type, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Ldsfld)
			{
				var fld = (FieldInfo)operands[0];
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { fld, isVolatile, unaligned },
							Replay = emit => emit.LoadField(fld, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Ldsflda)
			{
				var fld = (FieldInfo)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { fld },
							Replay = emit => emit.LoadFieldAddress(fld)
						};
			}

			if (op == OpCodes.Ldstr)
			{
				var str = (String)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { str },
							Replay = emit => emit.LoadConstant(str)
						};
			}

			if (op == OpCodes.Ldtoken)
			{
				var asFld = operands[0] as FieldInfo;
				var asMtd = operands[0] as MethodInfo;
				var asType = operands[0] as Type;

				if (asFld != null)
				{
					return
							new Operation<DelegateType>
							{
								OpCode = op,
								Parameters = new Object[] { asFld },
								Replay = emit => emit.LoadConstant(asFld)
							};
				}

				if (asMtd != null)
				{
					return
							new Operation<DelegateType>
							{
								OpCode = op,
								Parameters = new Object[] { asMtd },
								Replay = emit => emit.LoadConstant(asMtd)
							};
				}

				if (asType != null)
				{
					return
							new Operation<DelegateType>
							{
								OpCode = op,
								Parameters = new Object[] { asType },
								Replay = emit => emit.LoadConstant(asType)
							};
				}

				throw new Exception("Unexpected operand for ldtoken [" + operands[0] + "]");
			}

			if (op == OpCodes.Ldvirtftn)
			{
				var mtd = (MethodInfo)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { mtd },
							Replay = emit => emit.LoadVirtualFunctionPointer(mtd)
						};
			}

			if (op == OpCodes.Leave || op == OpCodes.Leave_S)
			{
				var absAddr = (Int32)operands[0];
				var label = ChooseLabel(absAddr, labels, labelAccumulator);

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { label },
							Replay = emit => emit.Leave(label)
						};
			}

			if (op == OpCodes.Localloc)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.LocalAllocate()
						};
			}

			if (op == OpCodes.Mkrefany)
			{
				var type = (Type)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.MakeReferenceAny(type)
						};
			}

			if (op == OpCodes.Mul)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Multiply()
						};
			}

			if (op == OpCodes.Mul_Ovf)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.MultiplyOverflow()
						};
			}

			if (op == OpCodes.Mul_Ovf_Un)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.UnsignedMultiplyOverflow()
						};
			}

			if (op == OpCodes.Neg)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Negate()
						};
			}

			if (op == OpCodes.Newarr)
			{
				var type = (Type)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.NewArray(type)
						};
			}

			if (op == OpCodes.Newobj)
			{
				var ctor = (ConstructorInfo)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { ctor },
							Replay = emit => emit.NewObject(ctor)
						};
			}

			if (op == OpCodes.Nop)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Nop()
						};
			}

			if (op == OpCodes.Not)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Not()
						};
			}

			if (op == OpCodes.Or)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Or()
						};
			}

			if (op == OpCodes.Pop)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Pop()
						};
			}

			if (op == OpCodes.Prefix1 || op == OpCodes.Prefix2 || op == OpCodes.Prefix3 || op == OpCodes.Prefix4 || op == OpCodes.Prefix5 || op == OpCodes.Prefix6 || op == OpCodes.Prefix7 || op == OpCodes.Prefixref)
			{
				throw new InvalidOperationException("Encountered reserved opcode [" + op + "]");
			}

			if (op == OpCodes.Readonly)
			{
				prefixes.SetReadOnly();
				return null;
			}

			if (op == OpCodes.Refanytype)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.ReferenceAnyType()
						};
			}

			if (op == OpCodes.Refanyval)
			{
				var type = (Type)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.ReferenceAnyValue(type)
						};
			}

			if (op == OpCodes.Rem)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Remainder()
						};
			}

			if (op == OpCodes.Rem_Un)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.UnsignedRemainder()
						};
			}

			if (op == OpCodes.Ret)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Return()
						};
			}

			if (op == OpCodes.Rethrow)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.ReThrow()
						};
			}

			if (op == OpCodes.Shl)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.ShiftLeft()
						};
			}

			if (op == OpCodes.Shr)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.ShiftRight()
						};
			}

			if (op == OpCodes.Shr_Un)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.UnsignedShiftRight()
						};
			}

			if (op == OpCodes.Sizeof)
			{
				var type = (Type)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.SizeOf(type)
						};
			}

			if (op == OpCodes.Starg)
			{
				UInt16 ix = (UInt16)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { ix },
							Replay = emit => emit.StoreArgument(ix)
						};
			}

			if (op == OpCodes.Starg_S)
			{
				UInt16 ix = (Byte)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { ix },
							Replay = emit => emit.StoreArgument(ix)
						};
			}

			if (op == OpCodes.Stelem)
			{
				var type = (Type)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.StoreElement(type)
						};
			}

			if (op == OpCodes.Stelem_I)
			{
				var type = typeof(IntPtr);
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.StoreElement(type)
						};
			}

			if (op == OpCodes.Stelem_I1)
			{
				var type = typeof(sbyte);
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.StoreElement(type)
						};
			}

			if (op == OpCodes.Stelem_I2)
			{
				var type = typeof(Int16);
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.StoreElement(type)
						};
			}

			if (op == OpCodes.Stelem_I4)
			{
				var type = typeof(Int32);
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.StoreElement(type)
						};
			}

			if (op == OpCodes.Stelem_I8)
			{
				var type = typeof(Int64);
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.StoreElement(type)
						};
			}

			if (op == OpCodes.Stelem_R4)
			{
				var type = typeof(Single);
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.StoreElement(type)
						};
			}

			if (op == OpCodes.Stelem_R8)
			{
				var type = typeof(Double);
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.StoreElement(type)
						};
			}

			if (op == OpCodes.Stelem_Ref)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = null,
							Replay = emit => emit.StoreElement<WildcardType>()
						};
			}

			if (op == OpCodes.Stfld)
			{
				var fld = (FieldInfo)operands[0];
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { fld, isVolatile, unaligned },
							Replay = emit => emit.StoreField(fld, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Stind_I)
			{
				var type = typeof(IntPtr);
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type, isVolatile, unaligned },
							Replay = emit => emit.StoreIndirect(type, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Stind_I1)
			{
				var type = typeof(sbyte);
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type, isVolatile, unaligned },
							Replay = emit => emit.StoreIndirect(type, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Stind_I2)
			{
				var type = typeof(Int16);
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type, isVolatile, unaligned },
							Replay = emit => emit.StoreIndirect(type, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Stind_I4)
			{
				var type = typeof(Int32);
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type, isVolatile, unaligned },
							Replay = emit => emit.StoreIndirect(type, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Stind_I8)
			{
				var type = typeof(Int64);
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type, isVolatile, unaligned },
							Replay = emit => emit.StoreIndirect(type, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Stind_R4)
			{
				var type = typeof(Single);
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type, isVolatile, unaligned },
							Replay = emit => emit.StoreIndirect(type, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Stind_R8)
			{
				var type = typeof(Double);
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type, isVolatile, unaligned },
							Replay = emit => emit.StoreIndirect(type, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Stind_Ref)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = null,
							Replay = emit => emit.StoreIndirect<WildcardType>(),

							Prefixes = prefixes.Clone()
						};
			}

			if (op == OpCodes.Stloc)
			{
				UInt16 ix = (UInt16)operands[0];
				var loc = locals[ix];

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { loc },
							Replay = emit => emit.StoreLocal(loc)
						};
			}

			if (op == OpCodes.Stloc_0)
			{
				UInt16 ix = 0;
				var loc = locals[ix];

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { loc },
							Replay = emit => emit.StoreLocal(loc)
						};
			}

			if (op == OpCodes.Stloc_1)
			{
				UInt16 ix = 1;
				var loc = locals[ix];

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { loc },
							Replay = emit => emit.StoreLocal(loc)
						};
			}

			if (op == OpCodes.Stloc_2)
			{
				UInt16 ix = 2;
				var loc = locals[ix];

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { loc },
							Replay = emit => emit.StoreLocal(loc)
						};
			}

			if (op == OpCodes.Stloc_3)
			{
				UInt16 ix = 3;
				var loc = locals[ix];

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { loc },
							Replay = emit => emit.StoreLocal(loc)
						};
			}

			if (op == OpCodes.Stloc_S)
			{
				UInt16 ix = (Byte)operands[0];
				var loc = locals[ix];

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { loc },
							Replay = emit => emit.StoreLocal(loc)
						};
			}

			if (op == OpCodes.Stobj)
			{
				var type = (Type)operands[0];
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type, isVolatile, unaligned },
							Replay = emit => emit.StoreObject(type, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Stsfld)
			{
				var fld = (FieldInfo)operands[0];
				var isVolatile = prefixes.HasVolatile;
				var unaligned = prefixes.HasUnaligned ? prefixes.Unaligned : null;

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { fld, isVolatile, unaligned },
							Replay = emit => emit.StoreField(fld, isVolatile, unaligned)
						};
			}

			if (op == OpCodes.Sub)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Subtract()
						};
			}

			if (op == OpCodes.Sub_Ovf)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.SubtractOverflow()
						};
			}

			if (op == OpCodes.Sub_Ovf_Un)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.UnsignedSubtractOverflow()
						};
			}

			if (op == OpCodes.Switch)
			{
				var swLabls = new Label[operands.Length];

				for (var i = 0; i < operands.Length; i++)
				{
					var abs = (Int32)operands[i];
					var label = ChooseLabel(abs, labels, labelAccumulator);
					swLabls[i] = label;
				}

				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { swLabls },
							Replay = emit => emit.Switch(swLabls)
						};
			}

			if (op == OpCodes.Tailcall)
			{
				prefixes.SetTailCall();
				return null;
			}

			if (op == OpCodes.Throw)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Throw()
						};
			}

			if (op == OpCodes.Unaligned)
			{
				Byte u = (Byte)operands[0];
				prefixes.SetUnaligned(u);
				return null;
			}

			if (op == OpCodes.Unbox)
			{
				var type = (Type)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.Unbox(type)
						};
			}

			if (op == OpCodes.Unbox_Any)
			{
				var type = (Type)operands[0];
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[] { type },
							Replay = emit => emit.UnboxAny(type)
						};
			}

			if (op == OpCodes.Volatile)
			{
				prefixes.SetVolatile();

				return null;
			}

			if (op == OpCodes.Xor)
			{
				return
						new Operation<DelegateType>
						{
							OpCode = op,
							Parameters = new Object[0],
							Replay = emit => emit.Xor()
						};
			}

			throw new Exception("Unexpected opcode [" + op + "]");
		}

		private static Int64 ReadLong(Byte[] cil, Int32 at)
		{
			var a = (UInt32)(cil[at] | (cil[at + 1] << 8) | (cil[at + 2] << 16) | (cil[at + 3] << 24));
			var b = (UInt32)(cil[at + 4] | (cil[at + 5] << 8) | (cil[at + 6] << 16) | (cil[at + 7] << 24));

			return (((Int64)b) << 32) | a;
		}

		private static Int32 ReadInt(Byte[] cil, Int32 at)
		{
			return cil[at] | (cil[at + 1] << 8) | (cil[at + 2] << 16) | (cil[at + 3] << 24);
		}

		private static Int16 ReadShort(Byte[] cil, Int32 at)
		{
			return (Int16)(cil[at] | cil[at + 1]);
		}

		private static Single ReadFloat(Byte[] cil, Int32 at)
		{
			var arr = new Byte[4];
			arr[0] = cil[at];
			arr[1] = cil[at + 1];
			arr[2] = cil[at + 2];
			arr[3] = cil[at + 3];

			if (!BitConverter.IsLittleEndian)
			{
				Array.Reverse(arr);
			}

			return BitConverter.ToSingle(arr, 0);
		}

		private static Double ReadDouble(Byte[] cil, Int32 at)
		{
			var arr = new Byte[8];
			arr[0] = cil[at];
			arr[1] = cil[at + 1];
			arr[2] = cil[at + 2];
			arr[3] = cil[at + 3];
			arr[4] = cil[at + 4];
			arr[5] = cil[at + 5];
			arr[6] = cil[at + 6];
			arr[7] = cil[at + 7];

			if (!BitConverter.IsLittleEndian)
			{
				Array.Reverse(arr);
			}

			return BitConverter.ToDouble(arr, 0);
		}

		private static Single ReadSingle(Byte[] cil, Int32 at)
		{
			var arr = new Byte[4];
			arr[0] = cil[at];
			arr[1] = cil[at + 1];
			arr[2] = cil[at + 2];
			arr[3] = cil[at + 3];

			if (!BitConverter.IsLittleEndian)
			{
				Array.Reverse(arr);
			}

			return BitConverter.ToSingle(arr, 0);
		}

		private static Object[] ReadOperands(Module mod, OpCode op, Byte[] cil, Int32 instrStart, Int32 operandStart, IDictionary<Int32, Parameter> pLookup, IDictionary<Int32, Local> lLookup, ref Int32 advance)
		{
			switch (op.OperandType)
			{
				case OperandType.InlineBrTarget:
					advance += 4;
					var offset = ReadInt(cil, operandStart);
					var jumpTarget = instrStart + advance + offset;
					return new Object[] { jumpTarget };

				case OperandType.InlineSwitch:
					advance += 4;
					var len = ReadInt(cil, operandStart);
					var offset1 = instrStart + len * 4;
					var ret = new Object[len];
					for (var i = 0; i < len; i++)
					{
						var step = ReadInt(cil, operandStart + advance);
						advance += 4;
						ret[i] = offset1 + step;
					}
					return ret;

				case OperandType.ShortInlineBrTarget:
					advance += 1;
					var offset2 = (sbyte)cil[operandStart];
					var jumpTarget2 = instrStart + advance + offset2;
					return new Object[] { jumpTarget2 };

				case OperandType.InlineField:
				case OperandType.InlineTok:
				case OperandType.InlineType:
				case OperandType.InlineMethod:
					advance += 4;
					var mem = mod.ResolveMember(ReadInt(cil, operandStart));
					return new Object[] { mem };

				case OperandType.InlineI:
					advance += 4;
					return new Object[] { ReadInt(cil, operandStart) };

				case OperandType.InlineI8:
					advance += 8;
					return new Object[] { ReadLong(cil, operandStart) };

				case OperandType.InlineNone:
					return new Object[0];

				case OperandType.InlineR:
					advance += 8;
					return new Object[] { ReadDouble(cil, operandStart) };

				case OperandType.InlineSig:
					advance += 4;
					var sig = mod.ResolveSignature(ReadInt(cil, operandStart));
					return new Object[] { sig };

				case OperandType.InlineString:
					advance += 4;
					var str = mod.ResolveString(ReadInt(cil, operandStart));
					return new Object[] { str };

				case OperandType.InlineVar:
					advance += 2;
					return new Object[] { ReadShort(cil, operandStart) };

				case OperandType.ShortInlineI:
					advance += 1;
					if (op == OpCodes.Ldc_I4_S)
					{
						return new Object[] { (sbyte)cil[operandStart] };
					}
					else
					{
						return new Object[] { cil[operandStart] };
					}

				case OperandType.ShortInlineR:
					advance += 4;
					if (op == OpCodes.Ldc_R4)
					{
						return new Object[] { ReadSingle(cil, operandStart) };
					}
					else
					{
						return new Object[] { ReadShort(cil, operandStart) };
					}

				case OperandType.ShortInlineVar:
					advance += 1;
					return new Object[] { cil[operandStart] };

				default: throw new Exception("Unexpected operand type [" + op.OperandType + "]");
			}
		}
	}
}